import React, {
  forwardRef,
  ReactNode,
  type RefObject,
  useCallback,
  useEffect,
  useImperativeHandle,
  useMemo,
  useRef,
  useState,
} from 'react'
import ReactDom from 'react-dom'
import classnames from 'classnames'
import styles from './index.module.scss'

export interface DropDownProps {
  // 容器id 默认是documentElement
  // 传入一个容器元素，弹框只会出现在该元素范围之内
  rootId?: string
  // 显示隐藏
  visible?: boolean
  // 控制初始显示
  defaultVisible?: boolean
  // 显示内容
  content?: React.ReactElement
  // 容器固定宽度 你可以传入一个固定值作为容器的宽度
  width?: number
  // 是否使用rootId元素的宽度作为width值
  useRootWidth?: boolean
  // 指定容器最大宽度
  bodyMaxWidth?: number
  // 容器内边距，默认为16px
  bodyPadding?: string
  // 容器高度，默认最大高度为332px
  bodyHeight?: number
  // 容器最大高度
  bodyMaxHeight?: number
  // 显示隐藏变化
  onChange?: (visible: boolean) => void
  // 下拉触发元素
  children?: React.ReactNode
  // 触发方式 默认hover
  trigger?: 'hover' | 'click'
  // 自定义类名
  className?: string
  // 内容插入到body下
  insertToBody?: boolean
  // 样式风格
  theme?: 'default' | 'border' | 'no-pointer'
}

interface IPosInfo {
  // 根容器宽度
  rootWidth: number
  // 箭头指示器距离容器左侧距离
  pointerLeft: number
  // 在不插入到body下时 容器left值
  left: number
  // 在不插入到body下时 容器top值
  top: number
  // 在插入到body下时 容器left值
  bodyLeft: number
  // 在插入到body下时 容器top值
  bodyTop: number
  // trigger元素宽度
  triggerWidth: number
  // 横条指示器距离容器左侧距离 theme=border时用到
  borderIndicatorLeft: number
}

export type DropDownActions = {
  hide: () => void
  show: () => void
}

export type DropDownRef = DropDownActions

const validateRef = (ref?: RefObject<any>): boolean => {
  return ref && ref.current
}

/**
 * 通用弹出浮框
 * */
export const DropDown = forwardRef<DropDownRef, DropDownProps>((props, ref) => {
  const {
    rootId,
    visible,
    defaultVisible,
    content,
    trigger = 'hover',
    width,
    useRootWidth,
    bodyMaxWidth,
    bodyPadding,
    bodyHeight,
    bodyMaxHeight,
    onChange,
    className,
    insertToBody,
    theme = 'default',
  } = props
  // 首次渲染
  const isMount = useRef(true)
  // 下拉触发元素
  const triggerRef = useRef<HTMLDivElement>(null as unknown as HTMLDivElement)
  // 下拉容器元素
  const containerRef = useRef<HTMLDivElement>(null as unknown as HTMLDivElement)
  // 控制显示隐藏
  const [innerVisible, setInnerVisible] = useState(!!defaultVisible)
  // 容器渲染宽度
  const [bodyLayoutWidth, setBodyLayoutWidth] = useState(0)
  // 位置信息
  const [result, setResult] = useState<IPosInfo>({
    rootWidth: 0,
    pointerLeft: 0,
    left: 0,
    top: 0,
    bodyLeft: 0,
    bodyTop: 0,
    triggerWidth: 0,
    borderIndicatorLeft: 0,
  })

  const hide = () => {
    setInnerVisible(false)
  }

  const show = () => {
    setInnerVisible(true)
  }

  useImperativeHandle(ref, () => ({
    hide,
    show,
  }))

  // 计算位置信息
  const getPosInfo = useCallback(() => {
    if (triggerRef && triggerRef.current) {
      // 容器距离左侧距离
      let left: number = 0
      // 指示符号距离左侧距离
      let pointerLeft: number = 0
      // 横条指示器距离容器左侧距离
      let borderIndicatorLeft: number = 0
      // 页面根容器的位置尺寸信息
      let rootDom

      if (rootId) {
        rootDom = document.getElementById(rootId)
      }
      if (!rootDom) {
        rootDom = document.body
      }

      const rootRect = rootDom.getBoundingClientRect()
      const bodyRect = document.body.getBoundingClientRect()
      // 下拉容器最大宽度
      const rootWidth = rootRect.width
      // 计算triggerDom的位置尺寸信息
      const triggerRect = triggerRef.current.getBoundingClientRect()
      // 计算下拉内容区域的宽度
      let containerWidth = 0
      if (width) {
        containerWidth = width
      } else if (useRootWidth) {
        containerWidth = rootWidth
      } else {
        containerWidth = Math.max(bodyLayoutWidth, 120) // 容器最小宽度为 120
      }

      const triggerCenterLeft = triggerRect.left + triggerRect.width / 2
      const containerCenterLeft = triggerRect.left + containerWidth / 2
      // 向左可以移动的距离
      const canLeft = (triggerRect.left - rootRect.left) | 0

      // 向左需要移动的距离
      let needLeft = 0
      if (canLeft + containerWidth > rootWidth) {
        needLeft = Math.min(canLeft + containerWidth - rootWidth, canLeft) | 0
      } else {
        needLeft = Math.min(containerCenterLeft - triggerCenterLeft, canLeft) | 0
      }

      pointerLeft = triggerRect.width / 2 - 8 + needLeft
      borderIndicatorLeft = needLeft

      left = -needLeft

      const info: IPosInfo = {
        rootWidth,
        pointerLeft,
        left,
        top: 0,
        bodyLeft: triggerRect.left - needLeft - bodyRect.left,
        bodyTop: triggerRect.top + triggerRect.height - bodyRect.top,
        triggerWidth: triggerRect.width,
        borderIndicatorLeft,
      }
      setResult(info)
    }
  }, [rootId, width, bodyLayoutWidth, useRootWidth])

  useEffect(() => {
    if (typeof visible === 'boolean') {
      if (visible) {
        // 计算位置
        getPosInfo()
      }
      setInnerVisible(visible)
    }
  }, [visible, getPosInfo])

  useEffect(() => {
    if (isMount.current) {
      isMount.current = false
    } else {
      onChange && onChange(innerVisible)
    }
  }, [innerVisible])

  useEffect(() => {
    // 当点击页面其他位置时隐藏
    const documentHandler = (event: Event) => {
      if (
        validateRef(containerRef) &&
        validateRef(triggerRef) &&
        !containerRef.current.contains(event.target as Node) &&
        !triggerRef.current.contains(event.target as Node)
      ) {
        setInnerVisible(false)
      }
    }

    document.addEventListener('mousedown', documentHandler)
    return () => {
      document.removeEventListener('mousedown', documentHandler)
    }
  }, [])

  useEffect(() => {
    if (innerVisible && containerRef && containerRef.current) {
      const w = containerRef.current.getBoundingClientRect().width | 0
      setBodyLayoutWidth(w)
    }
  }, [innerVisible])

  useEffect(() => {
    if (!innerVisible) return

    let observer: MutationObserver
    const _w = window as any
    const MutationObserver = _w.MutationObserver || _w.WebKitMutationObserver || _w.MozMutationObserver

    // 下一帧执行计算
    let block = false
    const inner = () => {
      if (!block) {
        block = true
        window.requestAnimationFrame(() => {
          getPosInfo()
          block = false
        })
      }
    }

    window.addEventListener('scroll', inner)
    window.addEventListener('resize', inner)
    if (triggerRef.current && MutationObserver) {
      observer = new MutationObserver(getPosInfo)
      // 监听触发元素的内容变动
      observer.observe(triggerRef.current, {
        characterData: true,
        childList: true,
        subtree: true,
      })
    }

    return () => {
      window.removeEventListener('scroll', getPosInfo)
      window.removeEventListener('resize', getPosInfo)
      observer && observer.disconnect()
    }
  }, [getPosInfo, innerVisible])

  const handleMouseEnterTrigger = () => {
    if (trigger === 'hover') {
      // 计算尺寸
      getPosInfo()
      setInnerVisible(true)
    }
  }

  const handleMouseLeaveTrigger = () => {
    trigger === 'hover' && setInnerVisible(false)
  }

  const styleMap = useMemo(() => {
    // 下拉容器样式
    const downWrapStyle: any = {}
    // 指示箭头样式
    const pointerStyle: any = {}
    // 下拉容器body样式
    const downBodyStyle: any = {}
    // 横条指示器样式
    const borderIndicatorStyle: any = {}

    // 如果传width优先使用width
    if (width) {
      downBodyStyle.width = `${width}px`
    } else if (useRootWidth) {
      downBodyStyle.width = `${result.rootWidth}px`
    } else if (bodyMaxWidth) {
      downBodyStyle.maxWidth = `${bodyMaxWidth}px`
      downBodyStyle.width = 'max-content'
    }

    if (bodyPadding) {
      downBodyStyle.padding = bodyPadding
    } else {
      downBodyStyle.padding = '16px'
    }

    if (bodyHeight) {
      downBodyStyle.height = `${bodyHeight}px`
    } else {
      downBodyStyle.maxHeight = '332px'
      downBodyStyle.overflow = 'auto'
    }

    if (bodyMaxHeight) {
      downBodyStyle.maxHeight = `${bodyMaxHeight}px`
      downBodyStyle.overflow = 'auto'
    }

    // 插入到body
    if (insertToBody) {
      downWrapStyle.left = `${result.bodyLeft}px`
      downWrapStyle.top = `${result.bodyTop}px`
    } else {
      downWrapStyle.left = `${result.left}px`
    }

    pointerStyle.left = `${result.pointerLeft}px`
    pointerStyle.top = '5px'

    borderIndicatorStyle.left = `${result.borderIndicatorLeft}px`
    borderIndicatorStyle.width = `${result.triggerWidth}px`

    return {
      downWrapStyle,
      pointerStyle,
      downBodyStyle,
      borderIndicatorStyle,
    }
  }, [result, insertToBody, width, useRootWidth, bodyMaxWidth, bodyPadding, bodyHeight, bodyMaxHeight])

  // 默认风格
  const themeDefault = (
    <div ref={containerRef} className={styles['drop-wrap']} style={styleMap.downWrapStyle}>
      <div className={styles.pointer} style={styleMap.pointerStyle}></div>
      <div className={styles['drop-down-body']} style={styleMap.downBodyStyle}>
        {content}
      </div>
    </div>
  )

  // 带边框风格
  const themeBorder = (
    <div
      ref={containerRef}
      className={classnames(styles['drop-wrap'], styles['drop-wrap__border'])}
      style={styleMap.downWrapStyle}
    >
      <div className={styles['border-indicator']} style={styleMap.borderIndicatorStyle}></div>
      <div className={styles['drop-down-body']} style={styleMap.downBodyStyle}>
        {content}
      </div>
    </div>
  )

  // 不带指示箭头
  const noPointer = (
    <div ref={containerRef} className={styles['drop-wrap']} style={{ ...styleMap.downWrapStyle, paddingTop: '3px' }}>
      <div className={styles['drop-down-body']} style={styleMap.downBodyStyle}>
        {content}
      </div>
    </div>
  )

  const themes = {
    default: themeDefault,
    border: themeBorder,
    'no-pointer': noPointer,
  }

  const dropDownCont = insertToBody ? ReactDom.createPortal(themes[theme], document.body) : themes[theme]

  return (
    <>
      <div
        onMouseEnter={handleMouseEnterTrigger}
        onMouseLeave={handleMouseLeaveTrigger}
        className={classnames(styles['trigger-item'], className ? (className as any) : '')}
      >
        <div ref={triggerRef}>{props.children}</div>
        {innerVisible ? dropDownCont : null}
      </div>
    </>
  ) as ReactNode
})
